#include "touch.h"
#include "ExcepTouch.h"
#include "touch_info.h"

#include <fstream>
#include <unordered_set>

const std::unordered_set<std::string> Header_Extensions{".h", ".hpp", ".h++", ".inl"};   // 头文件扩展名集合
const std::unordered_set<std::string> Source_Extensions{".cpp", ".cxx", ".c++", ".c", ".cc"};

namespace tinytool
{
namespace touch
{

const std::string HEAD_CONTENT_PRE = "";
const std::string HEAD_CONTENT_SUF = "";
const std::string HEAD_PRAGMA_ONCE = "#pragma once";

static void upper(std::string& s)
{
    for (char& c : s) c = toupper(c);
}

// 将 file 中的非字母数字字符转为下划线
static void legalize(std::string& file)
{
    for (char& c : file)
        if (!isalnum(c)) c = '_';
}

// header file content
static std::string head_content(const FS::path& path, std::string project)
{
    std::string name = path.filename().string();
    legalize(name);
    upper(name);
    project += "_";
    upper(project);
    return HEAD_CONTENT_PRE + project + name + HEAD_CONTENT_SUF;
}

static bool is_header_path(const FS::path& path)
{
    return Header_Extensions.count(path.extension().string());
}

static bool is_source_path(const FS::path& path)
{
    return Source_Extensions.count(path.extension().string());
}

static void write_namespace(std::ostream& ofs, const std::vector<std::string>& namespaces)
{
    for (auto it : namespaces) {
        ofs << "namespace " << it << "\n{\n";
    }
    if (!namespaces.empty()) ofs << "\n\n";
    for (auto it : namespaces) {
        ofs << "}   // namespace " << it << "\n";
    }
}

static std::string filename(const FS::path& path, bool have_extension = false)
{
    std::string name      = path.filename().string();
    std::string extension = path.extension().string();
    return (have_extension) ? name : name.substr(0, name.rfind(extension));
}

void touch(const FS::path& path, const std::vector<std::string>& namespaces,
           const std::string& project_name, bool is_force, bool create_directory)
{
    auto parent = path.parent_path();
    if (!parent.empty() && !FS::exists(parent)) {
        if (!create_directory) {
            cout_warn(TOUCH_PROGRAM_NAME, "No such directory" + mark_path(parent) + ", you can create it using option" + mark("-r"));
            return;
        }
        else
            FS::create_directories(parent);
    }
    if (FS::is_regular_file(path)) {
        if (!is_force) {
            cout_warn(TOUCH_PROGRAM_NAME, "the file already exist" + mark_path(path));
            return;
        }
    }
    std::ofstream ofs{path};
    if (!ofs) throw ExcepTouch{"not open file" + mark_path(path)};
    if (is_header_path(path)) {
        if (project_name.empty()) {
            ofs << HEAD_PRAGMA_ONCE << "\n\n\n";
            write_namespace(ofs, namespaces);
        }
        else {
            auto con = head_content(path, project_name);
            ofs << "#ifndef " << con << "\n#define " << con << "\n\n\n";
            write_namespace(ofs, namespaces);
            ofs << "\n#endif   // !" << con << std::endl;
        }
    }
    else if (is_source_path(path)) {
        ofs << "#include \"" << filename(path) << ".h\""
            << "\n\n\n";
        write_namespace(ofs, namespaces);
    }
}

}   // namespace touch
}   // namespace tinytool
